package com.qs.cwams.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

import com.qs.common.framework.dto.PageResult;
import com.qs.common.framework.dto.QsPage;
import com.qs.common.framework.dto.QsResult;
import com.qs.common.framework.util.ConvertUtil;
import com.qs.common.framework.util.RedisUtils;
import com.qs.cwams.mapper.*;
import com.qs.cwams.pojo.domain.*;
import com.qs.cwams.pojo.view.ApplyVO;
import com.qs.cwams.pojo.view.AtteDateVO;
import com.qs.cwams.service.ApplyService;
import com.qs.cwams.service.AtteRecordService;
import com.qs.cwams.util.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * @author suncheng
 */
@Service
public class ApplyServiceImpl implements ApplyService {

    @Autowired
    private ApplyMapper applyMapper;
    @Autowired
    private ApplyDetailMapper applyDetailMapper;
    @Autowired
    private AtteConfigMapper atteConfigMapper;
    @Autowired
    private AtteRecordMapper atteRecordMapper;
    @Autowired
    private AtteRecordService atteRecordService;
    @Autowired
    private RedisUtils redisUtils;

    private void setDetailOfDay(LocalDate date, LocalDateTime startTime, LocalDateTime endTime, ApplyDO apply) {
        if (Duration.between(startTime, endTime).toMinutes() <= 0) {
            return;
        }
        List<ApplyDetailDO> details = new ArrayList<>();
        AtteDailyTime daily = new AtteDailyTime(date);
        TimeSlot leave = new TimeSlot(startTime, endTime);
        if (daily.getWorkTime().size() > 0) {
            for (TimeSlot work : daily.getWorkTime()) {
                TimeSlot time = work.intersect(leave);
                if (time != null) {
                    ApplyDetailDO detail = new ApplyDetailDO();
                    detail.setApplyId(apply.getId());
                    detail.setUserId(apply.getUserId());
                    detail.setType(apply.getType());
                    detail.setYear(date.getYear());
                    detail.setMonth(date.getMonth().getValue());
                    detail.setDay(date.getDayOfMonth());
                    detail.setStartTime(Date.from(time.getStartTime().atZone(ZoneId.systemDefault()).toInstant()));
                    detail.setEndTime(Date.from(time.getEndTime().atZone(ZoneId.systemDefault()).toInstant()));
                    details.add(detail);
                }
            }
        }

        for (ApplyDetailDO detail : details) {
            applyDetailMapper.insert(detail);
        }
    }

    private void addDetail(ApplyDO apply) {
        LocalDateTime start = LocalDateTime.ofInstant(apply.getStartTime().toInstant(), ZoneId.systemDefault());
        LocalDateTime end = LocalDateTime.ofInstant(apply.getEndTime().toInstant(), ZoneId.systemDefault());

        AtteStandard standard = new AtteStandard();
        long days = Duration.between(start, end).toDays();
        if (days == 0) {
            setDetailOfDay(start.toLocalDate(), start, end, apply);
        } else {
            setDetailOfDay(start.toLocalDate(), start, LocalDateTime.of(start.toLocalDate(), standard.getPmEndTime()), apply);
            for (int i = 1; i <= (days - 1); i++) {
                LocalDate date = start.toLocalDate().plusDays(i);
                setDetailOfDay(date, LocalDateTime.of(date, standard.getAmStartTime()),
                        LocalDateTime.of(date, standard.getPmEndTime()), apply);
            }
            setDetailOfDay(end.toLocalDate(), LocalDateTime.of(end.toLocalDate(), standard.getAmStartTime()), end, apply);
        }
    }

    private LocalDate getLocalDate(Date date) {
        LocalDateTime localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
        return localDateTime.toLocalDate();
    }

    private void updateRecord(ApplyDO apply) {
        AtteRecordDO record = new AtteRecordDO();
        record.setUserId(apply.getUserId());
        if (apply.getStartTime() != null && apply.getEndTime() != null) {
            LocalDate start = getLocalDate(apply.getStartTime());
            record.setAtteYear(start.getYear());
            record.setAtteMonth(start.getMonth().getValue());
            record.setAtteDay(start.getDayOfMonth());
            record.setStartTime(apply.getStartTime());
            record.setEndTime(apply.getEndTime());
            atteRecordService.update(record);
        } else if (apply.getStartTime() != null) {
            LocalDate start = getLocalDate(apply.getStartTime());
            record.setAtteYear(start.getYear());
            record.setAtteMonth(start.getMonth().getValue());
            record.setAtteDay(start.getDayOfMonth());
            record.setStartTime(apply.getStartTime());
            atteRecordService.update(record);
        } else if (apply.getEndTime() != null) {
            LocalDate end = getLocalDate(apply.getEndTime());
            record.setAtteYear(end.getYear());
            record.setAtteMonth(end.getMonth().getValue());
            record.setAtteDay(end.getDayOfMonth());
            record.setEndTime(apply.getEndTime());
            atteRecordService.update(record);
        }
    }

    private void execAtteResult(Long userId, LocalDate date) {
        LocalDate now = LocalDate.now();
        if (date.isBefore(now)) {
            AtteDateVO atteDateVO = new AtteDateVO();
            atteDateVO.setUserId(userId);
            atteDateVO.setYear(date.getYear());
            atteDateVO.setMonth(date.getMonth().getValue());
            atteDateVO.setDay(date.getDayOfMonth());
            atteRecordService.result(atteDateVO);
        }
    }

    private void handle(ApplyDO apply) {
        LocalDateTime start = LocalDateTime.ofInstant(apply.getStartTime().toInstant(), ZoneId.systemDefault());
        LocalDateTime end = LocalDateTime.ofInstant(apply.getEndTime().toInstant(), ZoneId.systemDefault());
        long days = Duration.between(start, end).toDays();
        if (days == 0) {
            execAtteResult(apply.getUserId(), start.toLocalDate());
        } else {
            for (int i = 0; i <= days; i++) {
                LocalDate date = start.plusDays(i).toLocalDate();
                execAtteResult(apply.getUserId(), date);
            }
        }
    }

    @Override
    public PageResult<ApplyVO> queryApply(ApplyVO applyVO, QsPage page) {
        Page<ApplyDO> applyPage = Page.of(page.getCurPage(), page.getPageSize());
        applyMapper.selectPage(applyPage, new LambdaQueryWrapper<ApplyDO>()
                .eq(applyVO.getStatus() != null, ApplyDO::getStatus, applyVO.getStatus())
                .eq(applyVO.getType() != null, ApplyDO::getType, applyVO.getType())
                .eq(applyVO.getUserId() != null, ApplyDO::getUserId, applyVO.getUserId())
                .eq(applyVO.getResult() != null, ApplyDO::getResult, applyVO.getResult())
                .eq(applyVO.getApprover() != null, ApplyDO::getApprover, applyVO.getApprover()));
        page.setTotal(applyPage.getTotal());
        List<ApplyVO> applies = ConvertUtil.listCopy(applyPage.getRecords(), ApplyVO.class);
        applies.forEach(apply -> {
            apply.setUserName(redisUtils.getUserName(apply.getUserId()));
            apply.setApproverName(redisUtils.getUserName(apply.getApprover()));
        });
        return new PageResult<>(applies, page);
    }

    @Override
    public QsResult apply(ApplyVO applyVO) {
        if (applyVO.getStartTime() == null && applyVO.getEndTime() == null) {
            return QsResult.fail(400, "申请时间错误");
        }

        // 判断是否有需要撤销的申请
        boolean delFlag = false;
        if (applyVO.getDelApplyId() != null) {
            ApplyDO old = applyMapper.selectById(applyVO.getDelApplyId());
            if (old.getStatus() == 1) {
                return QsResult.fail(400, "无法撤销已审批的申请");
            } else {
                delFlag = true;
            }
        }

        AtteConfigDO config = atteConfigMapper.selectOne(new LambdaQueryWrapper<AtteConfigDO>()
                .eq(AtteConfigDO::getConfigCode, "Approver"));
        ApplyDO applyDO = ConvertUtil.copy(applyVO, ApplyDO.class);
        applyDO.setStatus(0);
        applyDO.setApprover(Long.valueOf(config.getConfigValue()));
        applyDO.setCreateTime(new Date());
        QsResult res = QsResult.ok();
        int group = ApplyType.getGroup(applyVO.getType());
        if (group == 1) {
            // 请假申请处理
            res = askForLeave(applyDO);
        } else if (group == 2) {
            // 忘打卡申请处理
            res = askForForget(applyDO);
        } else if (group == 3) {
            // 加班申请处理
        }

        if (res.getCode() == 200) {
            applyMapper.insert(applyDO);
            if (delFlag) {
                applyMapper.deleteById(applyVO.getDelApplyId());
            }
        }
        return res;
    }

    @Override
    public QsResult approve(ApplyVO applyVO) {
        ApplyDO applyDO = applyMapper.selectById(applyVO.getId());
        if (applyDO == null) {
            return QsResult.fail(400, "未找到申请单");
        }
        if (applyDO.getStatus() == 1) {
            return QsResult.fail(400, "申请已审批");
        }
        applyDO.setResult(applyVO.getResult());
        applyDO.setApprover(applyVO.getApprover());
        applyDO.setRemark(applyVO.getRemark());
        applyDO.setApproveTime(new Date());
        applyDO.setStatus(1);
        int res = applyMapper.updateById(applyDO);
        if (applyDO.getResult() == 1) {
            int group = ApplyType.getGroup(applyDO.getType());
            if (group == 1) {
                // 请假审批处理
                addDetail(applyDO);
                handle(applyDO);
            } else if (group == 2) {
                // 忘打卡审批处理
                updateRecord(applyDO);
            } else if (group == 3) {
                // 加班审批处理
            }
        }
        return res == 1 ? QsResult.ok() : QsResult.fail();
    }

    @Override
    public QsResult askForLeave(ApplyDO applyDO) {
        if (applyDO.getStartTime() == null || applyDO.getEndTime() == null) {
            return QsResult.fail(400, "申请时间不能包含空");
        }

        if (applyDO.getStartTime().getTime() > applyDO.getEndTime().getTime()) {
            return QsResult.fail(400, "申请时间起始时间应小于结束时间");
        }

        // 计算请假时长
        CalcDuration calcDuration = new CalcDuration();
        long minutes = calcDuration.total(applyDO.getStartTime(), applyDO.getEndTime());
        double hours = Long.valueOf(minutes).doubleValue() / 60;
        applyDO.setHours(hours);
        return QsResult.ok();
    }

    @Override
    public QsResult askForForget(ApplyDO applyDO) {
        AtteConfigDO atteConfigDO = atteConfigMapper.selectOne(new LambdaQueryWrapper<AtteConfigDO>()
                .eq(AtteConfigDO::getConfigCode, "ForgetTimes"));
        if (atteConfigDO != null) {
            long times = Long.parseLong(atteConfigDO.getConfigValue());
            long count = applyMapper.selectCount(new LambdaQueryWrapper<ApplyDO>()
                    .eq(ApplyDO::getUserId, applyDO.getUserId())
                    .eq(ApplyDO::getStatus, 1)
                    .eq(ApplyDO::getResult, 1)
                    .eq(ApplyDO::getType, 7));
            if (count >= times) {
                return QsResult.fail(400, "已超过可申请忘打卡次数");
            }
        }

        LocalDate date;
        if (applyDO.getStartTime() == null && applyDO.getEndTime() == null) {
            return QsResult.fail(400, "至少有一个申请时间");
        } else if (applyDO.getStartTime() == null) {
            date = LocalDateTime.ofInstant(applyDO.getEndTime().toInstant(), ZoneId.systemDefault()).toLocalDate();
        } else if (applyDO.getEndTime() == null) {
            date = LocalDateTime.ofInstant(applyDO.getStartTime().toInstant(), ZoneId.systemDefault()).toLocalDate();
        } else {
            LocalDate end = LocalDateTime.ofInstant(applyDO.getEndTime().toInstant(), ZoneId.systemDefault()).toLocalDate();
            LocalDate start = LocalDateTime.ofInstant(applyDO.getStartTime().toInstant(), ZoneId.systemDefault()).toLocalDate();
            if (!start.equals(end)) {
               return QsResult.fail(400, "申请时间必须是同一天");
            } else {
                date = start;
            }
        }

        AtteRecordDO record = atteRecordMapper.selectOne(new LambdaQueryWrapper<AtteRecordDO>()
                .eq(AtteRecordDO::getUserId, applyDO.getUserId())
                .eq(AtteRecordDO::getAtteYear, date.getYear())
                .eq(AtteRecordDO::getAtteMonth, date.getMonth().getValue())
                .eq(AtteRecordDO::getAtteDay, date.getDayOfMonth()));
        if (record != null) {
            if ((record.getStartTime() != null || applyDO.getStartTime() != null) &&
                    (record.getEndTime() != null || applyDO.getEndTime() != null)) {
                applyDO.setHours(0D);
                return QsResult.ok();
            }
        }
        return QsResult.fail(400, "考勤记录与申请时间不完整");
    }
}
